package doh.service;

import java.util.*;
import javax.ejb.*;
import javax.annotation.*;
import javax.persistence.*;
import javax.persistence.criteria.*;
import doh.login.*;
import doh.entity.*;
import doh.logic.*;
import doh.util.*;
import java.io.*;

@Stateful
public class UserSessionBean implements UserSessionBeanRemote {

    @PersistenceContext
    private EntityManager em;
    private CriteriaBuilder cb;
    private String userid; // if user has logged in
    private LoginService ls;
    private RegistrationService rs;

    @PostConstruct
    void init(){
        cb = em.getCriteriaBuilder();
        ls = new LoginService(em, cb);
        rs = new RegistrationService(em, cb);
    }

    public String getId() {
        return new String(this.userid);
    }

    public boolean login(String id, String password){
        if(ls.login(id, password)){
            userid = id;
            return true;
        }
        return false;
    }

    public boolean register(String id, String password){
        if(rs.register(id, password)){
            userid = id;
            return true;
        }
        return false;
    }

    public List<Player> getPlayers() {
        List<Player> allPlayers = null;
        try {
            CriteriaQuery<Player> cq = cb.createQuery(Player.class);

            if (cq != null) {
                Root<Player> player = cq.from(Player.class);
                cq.select(player);
                TypedQuery<Player> q = em.createQuery(cq);
                allPlayers = q.getResultList();
            }
        } catch (Exception ex) {
            throw new EJBException(ex);
        }
        return (allPlayers);
    }

    public int getRating(String id) {
        Player myPlayer = em.find(Player.class, id);
        return myPlayer.get_rating();
    }

    public void adjust_stats(String p1, String p2, boolean winner, int adjustment, GameSessionDetails gsd) {
        Player player1 = em.find(Player.class, p1);
        Player player2 = em.find(Player.class, p2);
        if (winner) {
            player1.set_wins(player1.get_wins()+1);
            player1.set_rating(player1.get_rating()+adjustment);
            player2.set_rating(player2.get_rating()-adjustment);
        } else {
            player1.set_rating(player1.get_rating()-adjustment);
            player2.set_rating(player2.get_rating()+adjustment);
            player2.set_wins(player2.get_wins()+1);
        }
        player1.set_games(player1.get_games()+1);
        player1.set_rocks(player1.get_rocks()+gsd.getPlayer1().rock);
        player1.set_papers(player1.get_papers()+gsd.getPlayer1().paper);
        player1.set_scissors(player1.get_scissors()+gsd.getPlayer1().scissor);
        player2.set_games(player2.get_games()+1);
        player2.set_rocks(player2.get_rocks()+gsd.getPlayer2().rock);
        player2.set_papers(player2.get_papers()+gsd.getPlayer2().paper);
        player2.set_scissors(player2.get_scissors()+gsd.getPlayer2().scissor);
        player1.set_wratio((double)player1.get_wins()/player1.get_games());
        player2.set_wratio((double)player2.get_wins()/player2.get_games());
        player1.set_oratio((((player1.get_games()-1)*player1.get_oratio())+player2.get_wratio())/player1.get_games());
        player2.set_oratio((((player2.get_games()-1)*player2.get_oratio())+player1.get_wratio())/player2.get_games());
    }

    public int calc_ELO_adjustment(int r1, int r2, boolean winner) {
        int max_adj = 32;
        double multiplier = 1.0;
        if (winner)
            multiplier = (1/(1+Math.pow(10.0,(r1-r2)/400.0)));
        else
            multiplier = (1/(1+Math.pow(10.0,(r2-r1)/400.0)));
        int adjustment = (int)Math.round(max_adj * multiplier);
        return adjustment;
    }

    public void addGame(GameSessionDetails gsd) {
        String p1 = gsd.getPlayer1().name;
        String p2 = gsd.getPlayer2().name;
        int r1 = getRating(p1);
        int r2 = getRating(p2);
        boolean winner = gsd.getWinner();
        int adjustment = calc_ELO_adjustment(r1, r2, winner);
        adjust_stats(p1, p2, winner, adjustment, gsd);
        Game completedGame = new Game(p1, p2, r1, r2, winner, adjustment);
        em.persist(completedGame);
    }

    public List<Game> getGames() {
        List<Game> allGames = null;
        try {
            CriteriaQuery<Game> cq = cb.createQuery(Game.class);

            if (cq != null) {
                Root<Game> game = cq.from(Game.class);
                cq.select(game);
                TypedQuery<Game> q = em.createQuery(cq);
                allGames = q.getResultList();
            }
        } catch (Exception ex) {
            throw new EJBException(ex);
        }
        return (allGames);
    }

    public List<Game> getHistory(String player) {
        List<Game> allGames = getGames();
        List<Game> playerHistory = new ArrayList();
        Iterator itr = allGames.iterator();
        while (itr.hasNext()) {
            Game aGame = (Game)itr.next();
            if (player.equals(aGame.get_p1()) || player.equals(aGame.get_p2())) {
                playerHistory.add(aGame);
            }
        }
        return playerHistory;
    }

    public List<LadderEntry> getLadder() {
        List<LadderEntry> rpsLadder = new ArrayList();
        List<Player> allPlayers = getPlayers();
        Iterator itr = allPlayers.iterator();
        while (itr.hasNext()) {
            Player aPlayer = (Player)itr.next();
            rpsLadder.add(new LadderEntry(aPlayer.get_id(), aPlayer.get_rating()));
        }
/*        rpsLadder.add(new LadderEntry("david", 1616));
        rpsLadder.add(new LadderEntry("rupal", 1755));
        rpsLadder.add(new LadderEntry("lucas", 1926));
        rpsLadder.add(new LadderEntry("bureaucrat", 1190));
        rpsLadder.add(new LadderEntry("avalanche", 1440));
        rpsLadder.add(new LadderEntry("fist", 1322));
        rpsLadder.add(new LadderEntry("sleepy", 1376));
        rpsLadder.add(new LadderEntry("hungry", 1552));
*/
        Collections.sort(rpsLadder);
        return rpsLadder;
    }

    @Remove
    public void destroy(){
        LoginService.removeOnlineUser(userid);
    }

    @PreDestroy
    public void logout(){
        LoginService.removeOnlineUser(userid);
        this.userid = null;
    }


    //GAME LOGIC METHODS

    static boolean playerAwaiting = false;
    static String awaitingPlayer;
    static long lastGameSearch;
    static Vector games = new Vector();
    static int gameID = -1;
    static final long timeout = 10000;
    int mygid;

    public int matchPlayers(String player)
    {
        if(!LoginService.isUserOnline(player)){
            return -2;
        }
        if(playerAwaiting == false) { // first player
            playerAwaiting = true;
            lastGameSearch = System.currentTimeMillis();
            Thread.currentThread().setPriority(Thread.MIN_PRIORITY+1);
            awaitingPlayer = player;
            while(playerAwaiting){
                if(lastGameSearch+timeout < System.currentTimeMillis()){
                    playerAwaiting = false;
                    return -1;
                }
            }
            Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
            mygid = gameID;
            System.err.println(gameList.size());
            return gameID;
        } else { // second player
            gameList.add(new doh.logic.GameInstance(awaitingPlayer, player, em));
            gameID++;
            playerAwaiting = false;
            mygid = gameID;
            System.err.println(gameList.size());
            return gameID;
        }
    }

    static List<doh.logic.GameInstance> gameList = new ArrayList<GameInstance>();
    //Collections.synchronizedList(new ArrayList<doh.logic.GameInstance>());

    public Properties makeThrow(String player, int t, int gameID) throws FileNotFoundException, IOException {
        return gameList.get(mygid).throwObject(player, t);
    }

    public boolean deleteUser(String id){
        // referee is indestructible
        if(id.equals("referee")) return false;

        // cannot delete a player that is in a game
        for(int i=0; i<gameList.size(); i++){
            if(!gameList.get(i).getGameWon() &&
                    (gameList.get(i).getP1().equals(id) ||
                    gameList.get(i).getP2().equals(id))){
                return false;
            }
        }

        // remove user if he is online
        if(LoginService.isUserOnline(id)){
            LoginService.removeOnlineUser(id);
        }
        return this.rs.deleteUser(id);
    }

    public boolean changeUserPassword(String id, String password){
        return this.rs.changePassword(id, password);
    }

}
